
package com.hlkj.pay.app.order.impl;
/*
 * Hlpay-Plus aggregate payment system.
 * Copyright (c) 2024-2025 Hlpay Team Copyright has the right of final interpretation.
 */

import cn.hutool.core.date.DatePattern;
import com.hlkj.framework.common.util.collection.CollectionUtils;
import com.hlkj.framework.common.util.json.JsonUtils;
import com.hlkj.pay.app.order.IOrderStatisticsAppService;
import com.hlkj.pay.dto.order.ComplaintOrderQueryDto;
import com.hlkj.pay.dto.order.PayOrderQueryDto;
import com.hlkj.pay.dto.order.StatisticsDto;
import com.hlkj.pay.dto.pay.PayWayQueryDto;
import com.hlkj.pay.enums.AdEnum;
import com.hlkj.pay.enums.PayEnums;
import com.hlkj.pay.enums.PayOrderEnums;
import com.hlkj.pay.infrastructure.model.order.OrderStatisticsDO;
import com.hlkj.pay.infrastructure.model.pay.PayWayDO;
import com.hlkj.pay.service.order.IOrderComplaintService;
import com.hlkj.pay.service.order.IOrderStatisticsService;
import com.hlkj.pay.service.order.IPayOrderService;
import com.hlkj.pay.service.pay.IPayWayService;
import com.hlkj.pay.util.DateUtils;
import com.hlkj.pay.util.MerchantUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author HlpayTeam
 * @date 2024/10/24 12:41
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderStatisticsAppServiceImpl implements IOrderStatisticsAppService {

    private final IOrderStatisticsService statisticsService;

    private final IPayWayService payWayService;

    private final IOrderComplaintService orderComplaintService;

    private final IPayOrderService payOrderService;

    @Override
    public void processStatisticsMessage(List<StatisticsDto> statisticsList) {
        try {
            log.info("收到统计消息处理 size:{}", statisticsList.size());
            List<PayWayDO> payWayList = payWayService.queryList(new PayWayQueryDto());
            Map<String, PayWayDO> payWayMap = payWayList.stream().collect(Collectors.toMap(payWayDO -> payWayDO.getCode(), Function.identity(), (t1, t2) -> t1));
            Map<String, List<StatisticsDto>> snOrderMap = statisticsList.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getSn()));
            // 支付方式只按照大类统计
            Map<Integer, List<StatisticsDto>> payWayOrderMap = statisticsList.stream()
                    .collect(Collectors.groupingBy(statisticsDto -> payWayMap.get(statisticsDto.getPayWayCode()).getType()));
            Map<String, List<StatisticsDto>> appIdOrderMap = statisticsList.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getAppId()));
            Map<String, List<StatisticsDto>> mchChannelOrderMap = statisticsList.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getMchChannelCode()));
            Map<String, List<StatisticsDto>> channelOrderMap = statisticsList.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getChannelCode()));
            Map<String, List<StatisticsDto>> authOrderMap = statisticsList.stream().filter(dto -> dto.getAutCode() != null && !dto.getAutCode().isEmpty()).collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getAutCode()));

            // 商户维度
            snOrderMap.forEach((key, value) -> {
                Map<String, List<StatisticsDto>> snMap = value.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getSn()));
                snMap.forEach((sn, valueList) -> {
                    processTimeStatistics(sn, PayOrderEnums.ORDER_STATISTICS_TYPE.SN, key, valueList);
                });
                value.stream().forEach(v -> {
                    log.info("商户维度统计数据 sn:{},data:{}", v.getSn(), JsonUtils.toJsonStringNotNull(v));
                });
            });
            // 支付方式维度
            payWayOrderMap.forEach((key, value) -> {
                Map<String, List<StatisticsDto>> snMap = value.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getSn()));
                snMap.forEach((sn, valueList) -> {
                    processTimeStatistics(sn, PayOrderEnums.ORDER_STATISTICS_TYPE.PAY_TYPE, key.toString(), valueList);
                });
            });
            // app维度
            appIdOrderMap.forEach((key, value) -> {
                Map<String, List<StatisticsDto>> snMap = value.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getSn()));
                snMap.forEach((sn, valueList) -> {
                    processTimeStatistics(sn, PayOrderEnums.ORDER_STATISTICS_TYPE.APPID, key, valueList);
                });
            });
            // mchChannelCode维度
            mchChannelOrderMap.forEach((key, value) -> {
                Map<String, List<StatisticsDto>> snMap = value.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getSn()));
                snMap.forEach((sn, valueList) -> {
                    processTimeStatistics(sn, PayOrderEnums.ORDER_STATISTICS_TYPE.MCH_CHANNEL_CODE, key, valueList);
                });
            });

            // channelCode维度
            channelOrderMap.forEach((key, value) -> {
                Map<String, List<StatisticsDto>> snMap = value.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getSn()));
                snMap.forEach((sn, valueList) -> {
                    processTimeStatistics(sn, PayOrderEnums.ORDER_STATISTICS_TYPE.CHANNEL_CODE, key, valueList);
                });
            });

            //authOrderMap
            authOrderMap.forEach((key, value) -> {
                Map<String, List<StatisticsDto>> snMap = value.stream().collect(Collectors.groupingBy(statisticsDto -> statisticsDto.getSn()));
                snMap.forEach((sn, valueList) -> {
                    processTimeStatistics(sn, PayOrderEnums.ORDER_STATISTICS_TYPE.AUT_CODE, key, valueList);
                });
            });
        }
        catch (Exception e) {
            log.info("订单统计异常 sn:{}", e.getMessage(), e);
        }
    }

    /**
     * 不同维度的统计
     *
     * @param statisticsType
     * @param relationId
     * @param statisticsDtos
     */
    private void processTimeStatistics(String sn, PayOrderEnums.ORDER_STATISTICS_TYPE statisticsType, String relationId, List<StatisticsDto> statisticsDtos) {
        statisticsDtos.stream().forEach(statisticsDto -> {
            statisticsDto.setTime(coverTime(statisticsDto.getCreateTime()));
        });
        // 计算当前时间段
        // key 当前时间段
        Map<Long, List<StatisticsDto>> timeRangeMap = statisticsDtos.stream().collect(Collectors.groupingBy(adStatisticsDto -> adStatisticsDto.getTime()));
        Set<Long> timeKeys = timeRangeMap.keySet();
        List<OrderStatisticsDO> statisticsDbList = statisticsService.queryList(sn, statisticsType.getCode(), relationId, timeKeys);
        if (CollectionUtils.isEmpty(statisticsDbList)) {
            statisticsDbList = new ArrayList<>();
        }
        Map<Long, OrderStatisticsDO> timeKeyDbMap = statisticsDbList.stream()
                .collect(Collectors.toMap(adStatisticsDO -> adStatisticsDO.getTime(), Function.identity(), (t1, t2) -> t1));
        Set<Long> timeKeyDbs = timeKeyDbMap.keySet();
        timeRangeMap.forEach((key, value) -> {
            log.info("统计维度 sn:{},type:{},key:{}", sn, statisticsType.getMsg(), key);

            ComplaintOrderQueryDto complaintOrderQueryDto = new ComplaintOrderQueryDto();
            complaintOrderQueryDto.setStartQueryTime(DateUtils.shanghaiStartDate(DateUtils.dateToString(key, DatePattern.NORM_DATE_PATTERN), DatePattern.NORM_DATE_PATTERN));
            complaintOrderQueryDto.setEndQueryTime(DateUtils.shanghaiEndDate(DateUtils.dateToString(key, DatePattern.NORM_DATE_PATTERN), DatePattern.NORM_DATE_PATTERN));
            complaintOrderQueryDto.setSn(sn);
            Integer complaintCount = orderComplaintService.countBySn(complaintOrderQueryDto);

            Integer wxOrderCount = getWxCount(complaintOrderQueryDto.getStartQueryTime(), complaintOrderQueryDto.getEndQueryTime(), sn);

            // 存在 进行更新
            if (timeKeyDbs.contains(key)) {
                OrderStatisticsDO adOrderStatisticsDO = calcStatisticsDim(value);
                OrderStatisticsDO adStatistics = timeKeyDbMap.get(key);
                adOrderStatisticsDO.setId(adStatistics.getId());
                adOrderStatisticsDO.setComplainCount(complaintCount);
                adOrderStatisticsDO.setWxOrderCount(wxOrderCount);
                statisticsService.updateStatistics(adOrderStatisticsDO);
            }
            else {
                log.info("新增统计维度 sn:{},type:{},key:{}", sn, statisticsType.getMsg(), key);
                OrderStatisticsDO statisticsDim = calcStatisticsDim(value);
                statisticsDim.setTime(key);
                statisticsDim.setComplainCount(complaintCount);
                statisticsDim.setWxOrderCount(wxOrderCount);
                statisticsDim.setType(statisticsType.getCode());
                statisticsDim.setRelationId(relationId);
                statisticsDim.setSn(sn);
                try {
                    statisticsService.addStatistics(statisticsDim);
                }
                catch (Exception e) {
                    log.error("新增统计维度 fail type:{},relationId:{}, message:{}", statisticsType.getMsg(), relationId, e.getMessage(), e);
                    // type relationId 与time做了唯一索引 新增失败进行更新
                    OrderStatisticsDO orderStatisticsDO = statisticsService.queryOne(sn, statisticsType.getCode(), relationId, key);
                    if (orderStatisticsDO == null) {
                        statisticsService.addStatistics(statisticsDim);
                    }
                    else {
                        statisticsDim.setId(orderStatisticsDO.getId());
                        statisticsService.updateStatistics(statisticsDim);
                    }
                }
            }
        });
    }

    Integer getWxCount(long startQueryTime, long endQueryTime, String sn) {
        PayOrderQueryDto wxQueryCount = new PayOrderQueryDto();
        wxQueryCount.setStartQueryTime(startQueryTime);
        wxQueryCount.setEndQueryTime(endQueryTime);
        wxQueryCount.setSn(sn);
        wxQueryCount.setState(PayOrderEnums.PAY_ORDER_STATUS.SUCCESS.getCode());
        wxQueryCount.setPayType(PayEnums.PAY_TYPE.WECHAT.getType());
        return payOrderService.orderCount(wxQueryCount);
    }

    public static Long coverTime(Long originalTimestamp) {
        // 将时间戳转换为 Instant
        Instant instant = Instant.ofEpochMilli(originalTimestamp);

        // 使用东八区时区计算当天零点
        ZoneId zoneId = ZoneId.of("Asia/Shanghai");
        LocalDateTime localDateTime = instant.atZone(zoneId).toLocalDate().atStartOfDay();

        // 转换回时间戳
        return localDateTime.atZone(zoneId).toInstant().toEpochMilli();
    }

    OrderStatisticsDO calcStatisticsDim(List<StatisticsDto> adStatisticsList) {
        Map<Integer, List<StatisticsDto>> statisticsMap = adStatisticsList.stream().collect(Collectors.groupingBy(adStatisticsDto -> adStatisticsDto.getStatisticsType()));
        List<StatisticsDto> payOrderList = statisticsMap.get(AdEnum.STATISTICS_TYPE.PAY_ORDER.getCode());
        List<StatisticsDto> submitOrderList = statisticsMap.get(AdEnum.STATISTICS_TYPE.SUBMIT_ORDER.getCode());
        List<StatisticsDto> refundOrderList = statisticsMap.get(AdEnum.STATISTICS_TYPE.REFUND_ORDER.getCode());
        Integer payOrderCount = 0;
        Integer submitCount = 0;
        Integer refundCount = 0;
        Double payOrderAmount = new Double(0);
        Double feeAmount = new Double(0);
        Double channelFeeAmount = new Double(0);
        Double submitAmount = new Double(0);
        Double refundAmount = new Double(0);
        if (!CollectionUtils.isEmpty(payOrderList)) {
            payOrderCount = payOrderList.size();
            payOrderAmount = payOrderList.stream().filter(statisticsDto -> StringUtils.hasText(statisticsDto.getPayOrderAmount()))
                    .mapToDouble(statisticsDto -> new Double(statisticsDto.getPayOrderAmount())).sum();
            feeAmount = payOrderList.stream().filter(statisticsDto -> StringUtils.hasText(statisticsDto.getFeeAmount()))
                    .mapToDouble(statisticsDto -> new Double(statisticsDto.getFeeAmount())).sum();
            channelFeeAmount = payOrderList.stream().filter(statisticsDto -> StringUtils.hasText(statisticsDto.getChannelFeeAmount()))
                    .mapToDouble(statisticsDto -> new Double(statisticsDto.getChannelFeeAmount())).sum();
        }
        if (!CollectionUtils.isEmpty(submitOrderList)) {
            submitCount = submitOrderList.size();
            submitAmount = submitOrderList.stream().mapToDouble(adStatisticsDto -> new Double(adStatisticsDto.getSubmitOrderAmount())).sum();
        }
        if (!CollectionUtils.isEmpty(refundOrderList)) {
            refundCount = refundOrderList.size();
            refundAmount = refundOrderList.stream().mapToDouble(adStatisticsDto -> new Double(adStatisticsDto.getRefundAmount())).sum();
        }
        OrderStatisticsDO orderStatisticsDO = new OrderStatisticsDO();
        orderStatisticsDO.setSubmitOrderCount(submitCount);
        orderStatisticsDO.setSubmitOrderAmount(MerchantUtils.doubleToBigDecimal(submitAmount));
        orderStatisticsDO.setPayOrderCount(payOrderCount);
        orderStatisticsDO.setPayOrderAmount(MerchantUtils.doubleToBigDecimal(payOrderAmount));
        orderStatisticsDO.setFeeAmount(MerchantUtils.doubleToBigDecimal(feeAmount));
        orderStatisticsDO.setChannelFeeAmount(MerchantUtils.doubleToBigDecimal(channelFeeAmount));
        orderStatisticsDO.setRefundCount(refundCount);
        orderStatisticsDO.setRefundAmount(MerchantUtils.doubleToBigDecimal(refundAmount));
        return orderStatisticsDO;
    }

    public static void main(String[] args) {
        System.out.println(DateUtils.shanghaiStartDate(DateUtils.dateToString(1747756800000l, DatePattern.NORM_DATE_PATTERN), DatePattern.NORM_DATE_PATTERN));
    }
}
